#include <windows.h>

#include "stdafx.h"
#include "DelayHandler.h"

// Constructor: responsible for various initialization.
CQuery::CQuery()
{
	AffectCount = -1;
	ret = SQL_SUCCESS;
	// Allocate an environment handle and set the Version property.
	SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
	SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);
}

// Destroyer: The connection handle is released.
CQuery::~CQuery()
{
	if (hStmt)
	{
		SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
	}

	if (hDbc)
	{
		SQLDisconnect(hDbc);
	}

	if (hDbc)
	{
		SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
	}

	if (hEnv)
	{
		SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
	}
}

// After connecting the handle to the allocated connection handle as far as the command assigned.
// Type = 1: ConStr have the path to the MDB file. Path defaults to the current directory, look for the MDB.
// Type = 2: ConStr with the connection information for the SQL Server DSN has the path to the file.
//        You must specify the full path must be the path.
// Type = 3: SQLConnect function is directly connected to the DSN.
// Assigned to handle the connection or command returns FALSE on failure.
BOOL CQuery::Connect(int Type, char * ConStr, char * UID, char * PWD)
{
	SQLCHAR InCon[255];
	SQLCHAR OutCon[255];
	SQLSMALLINT cbOutCon;
	int ii = 1;
	SQLRETURN Ret;
	SQLINTEGER NativeError;
	SQLCHAR SqlState[6], Msg[255];
	SQLSMALLINT MsgLen;
	char str[256];
	m_Type = Type;
	strcpy(m_szConnect, ConStr);
	strcpy(m_Id, UID);
	strcpy(m_Pass, PWD);
	// MDB or SQL server, depending on the type of connection, or connect to the DSN.
	SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);

	switch(Type)
	{
	case 1:
		wsprintf((char *)InCon, "DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s;", ConStr);
		ret = SQLDriverConnect(hDbc, NULL, (SQLCHAR *)InCon, sizeof(InCon), OutCon,
			sizeof(OutCon), &cbOutCon, SQL_DRIVER_NOPROMPT);
		break;

	case 2:
		wsprintf((char *)InCon, "FileDsn=%s", ConStr);
		ret = SQLDriverConnect(hDbc, NULL, (SQLCHAR *)InCon, sizeof(InCon), OutCon,
			sizeof(OutCon), &cbOutCon, SQL_DRIVER_NOPROMPT);
		break;

	case 3:
		ret = SQLConnect(hDbc, (SQLCHAR *)ConStr, SQL_NTS, (SQLCHAR *)UID, SQL_NTS,
			(SQLCHAR *)PWD, SQL_NTS);
		break;
	}

	// Shows a connection error diagnostic information.
	if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO))
	{
		while (Ret = SQLGetDiagRec(SQL_HANDLE_DBC, hDbc, ii, SqlState, &NativeError,
			Msg, sizeof(Msg), &MsgLen) != SQL_NO_DATA)
		{
			wsprintf(str, "(1) SQLSTATE:%s, Diagnosis:%s", (LPCTSTR)SqlState, (LPCTSTR)Msg);
			//::MessageBox(NULL,str,"Diagnostic information",0);
			LogAddTD(str);
			ii++;
		}

		return FALSE;
	}

	// Command to assign the handle.
	ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);

	if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO))
	{
		hStmt = 0;
		return FALSE;
	}

	return TRUE;
}

BOOL CQuery::ReConnect()
{
	return Connect(m_Type, m_szConnect, m_Id, m_Pass);
}

// Execute the SQL statement. Failure to output diagnostic information returns FALSE.
BOOL CQuery::Exec(LPCTSTR szSQL)
{
	int c;

	while (true)
	{
		LogAddTD("%s", szSQL);
		// Execute the SQL statement. If success is returned SQL_NO_DATA be treated as one.
		// Fetch returns EOF in this case because the output diagnostic information is not required.
		ret = SQLExecDirect(hStmt, (SQLCHAR *)szSQL, SQL_NTS);

		if ((ret != SQL_SUCCESS) && (ret != SQL_SUCCESS_WITH_INFO) && (ret != SQL_NO_DATA))
		{
			bool bReConnect = false;
			PrintDiag(bReConnect);
			this->Clear();

			if (bReConnect == true)
			{
				::Sleep(1);
				continue;
			}

			return FALSE;
		}

		// Update, Delete, Insert obtained when the command sets the number of records affected.
		SQLRowCount(hStmt, &AffectCount);
		SQLNumResultCols(hStmt, &nCol);

		if (nCol > MAXCOL)
		{
			//::MessageBox(NULL,"You have exceeded the maximum number of columns","CQuery Error",MB_OK);
			LogAdd("CQuery error: You have exceeded the maximum number of columns.");
			return FALSE;
		}

		// nCol is zero other than a Select statement, so if you run the command
		// Binding is not required.
		if (nCol == 0)
		{
			Clear();
			return TRUE;
		}

		// It puts all of the columns is bound to a string. Col array is zero base,
		// note the number of columns that are one base
		for(c = 0; c < nCol; c++)
		{
			SQLBindCol(hStmt, c + 1, SQL_C_CHAR, Col[c], 255, &lCol[c]);
			SQLDescribeCol(hStmt, c + 1, ColName[c], 30, NULL, NULL, NULL, NULL, NULL);
		}

		return TRUE;
	}
}

// Executes an SQL statement in the first column of the result set to return an integer value gives a reading.
// It gives the results obtained from the theorem.
int CQuery::ExecGetInt(LPCTSTR szSQL)
{
	int i;

	//LogAdd("%s", szSQL);

	if (Exec(szSQL) == FALSE)
	{
		return CQUERYERROR;
	}

	// If no data is EOF is returned.
	if (Fetch() == SQL_NO_DATA)
	{
		Clear();
		return CQUERYEOF;
	}

	i = GetInt(1);
	Clear();
	return i;
}

int CQuery::ExecGetInt(char * szSQL, int * retvalue)
{
	LogAddTD("%s", szSQL);

	if (this->Exec(szSQL) == FALSE)
	{
		return FALSE;
	}

	if (this->Fetch() == SQL_NO_DATA)
	{
		this->Clear();
		return FALSE;
	}

	int i = this->GetInt(1);
	this->Clear();
	*retvalue = i;
	return TRUE;
}

// The SQL statement is executed and the result set returned the first column gives the string to read.
BOOL CQuery::ExecGetStr(LPCTSTR szSQL, char * buf)
{
	//	LogAdd("%s", szSQL);

	// If an error occurred in the execution of an SQL statement string and returns an error buffer.
	if (Exec(szSQL) == FALSE)
	{
		//strcpy(buf, "CQUERYERROR");
		buf[0] = '\0';
		return FALSE;
	}

	// If no data is EOF is returned.
	if (Fetch() == SQL_NO_DATA)
	{
		Clear();
		//strcpy(buf,"EOF");
		buf[0] = '\0';
		return FALSE;
	}

	GetStr(1, buf);
	Clear();
	return TRUE;
}

// Bring in a row in the result set.
SQLRETURN CQuery::Fetch()
{
	ret = SQLFetch(hStmt);
	return ret;
}

// Close the binding information, the cursor is released.
void CQuery::Clear()
{
	SQLCloseCursor(hStmt);
	::SQLFreeStmt(hStmt, SQL_UNBIND);
}

// Find the name of the column from the column index. -1 Is returned if no.
int CQuery::FindCol(char * name)
{
	int i;

	for(i = 0; i < nCol; i++)
	{
		if (stricmp(name, (LPCTSTR)ColName[i]) == 0)
		{
			return i + 1;
		}
	}

	return -1;
}

// nCol read the integer value of the column. If NULL is returned CQUERYNULL.
int CQuery::GetInt(int nCol)
{
	if (nCol > this->nCol)
	{
		return CQUERYNOCOL;
	}

	if (lCol[nCol - 1] == SQL_NULL_DATA)
	{
		return CQUERYNULL;
	}
	else
	{
		return atoi(Col[nCol - 1]);
	}
}

// sCol read the integer value of the column.
int CQuery::GetInt(char * sCol)
{
	int n;
	n = FindCol(sCol);

	if (n == -1)
	{
		return CQUERYNOCOL;
	}
	else
	{
		return GetInt(n);
	}
}

__int64 CQuery::GetInt64(int nCol)
{
	if (nCol > this->nCol)
	{
		return CQUERYNOCOL;
	}

	if (lCol[nCol - 1] == SQL_NULL_DATA)
	{
		return CQUERYNULL;
	}
	else
	{
		return _atoi64(Col[nCol - 1]);
	}
}

__int64 CQuery::GetInt64(char * sCol)
{
	int n;
	n = FindCol(sCol);

	if (n == -1)
	{
		return CQUERYNOCOL;
	}
	else
	{
		return GetInt64(n);
	}
}

// nCol accidentally read the column value. If NULL is returned CQUERYNULL.
float CQuery::GetFloat(int nCol)
{
	if (nCol > this->nCol)
	{
		return CQUERYNOCOL;
	}

	if (lCol[nCol - 1] == SQL_NULL_DATA)
	{
		return CQUERYNULL;
	}
	else
	{
		return (float)atof(Col[nCol - 1]);
	}
}

// sCol accidentally read the column value.
float CQuery::GetFloat(char * sCol)
{
	int n;
	n = FindCol(sCol);

	if (n == -1)
	{
		return CQUERYNOCOL;
	}
	else
	{
		return GetFloat(n);
	}
}

// nCol read the column value as a string. If NULL, NULL, fills the string.
// The length of buf length should be at least 256 is not checked.
void CQuery::GetStr(int nCol, char * buf)
{
	if (nCol > this->nCol)
	{
		//strcpy(buf, "CQUERYNOCOL");
		buf[0] = '\0';
		return;
	}

	if (lCol[nCol - 1] == SQL_NULL_DATA)
	{
		buf[0] = '\0';
		//lstrcpy(buf,"NULL");
	}
	else
	{
		lstrcpy(buf, Col[nCol - 1]);
	}
}

// sCol read the column value as a string.
void CQuery::GetStr(char * sCol, char * buf)
{
	int n;
	n = FindCol(sCol);

	if (n == -1)
	{
		//lstrcpy(buf,"ERROR:Colume not found");
		buf[0] = '\0';
	}
	else
	{
		GetStr(n, buf);
	}
}

// It gives the output of diagnostic information when an error occurs.
void CQuery::PrintDiag(bool & bReconnect)
{
	int ii;
	SQLRETURN Ret;
	SQLINTEGER NativeError;
	SQLCHAR SqlState[6], Msg[255];
	SQLSMALLINT MsgLen;
	char str[256];
	ii = 1;

	while (Ret = SQLGetDiagRec(SQL_HANDLE_STMT, hStmt, ii, SqlState, &NativeError,
		Msg, sizeof(Msg), &MsgLen) != SQL_NO_DATA)
	{
		wsprintf(str, "(2) SQLSTATE:%s, Diagnosis:%s", (LPCTSTR)SqlState, (LPCTSTR)Msg);
		//::MessageBox(NULL,str,"Diagnostic information",0);
		LogAddTD(str);
		//LogAdd(str);
		ii++;

		if (ii > 3)
		{
			break;
		}
	}

	// If the error is a communication error. Connect again look.
	if (strcmp((LPCTSTR)SqlState, "08S01") == 0)
	{
		if (ReConnect() == 1)
		{
			g_DelayHandler.IncreaseQuerySessionId();
		}

		bReconnect = true;
	}
}

// BLOB data fills in buf. The buf is large enough to put a pre-allocated memory.
// If NULL, returns 0 and -1 is returned when an error occurs.
// On success returns the total number of bytes read.
// BLOB fields is one of szSQL Select SQL statements should be read.
int CQuery::ReadBlob(LPCTSTR szSQL, void * buf)
{
	SQLCHAR BinaryPtr[BLOBBATCH];
	SQLINTEGER LenBin;
	char * p;
	int nGet;
	int TotalGet = 0;

	//LogAdd("%s", szSQL);

	while (true)
	{
		ret = SQLExecDirect(hStmt, (SQLCHAR *)szSQL, SQL_NTS);

		if (ret != SQL_SUCCESS)
		{
			bool bReconnect = false;
			PrintDiag(bReconnect);

			if (bReconnect == true)
			{
				::Sleep(1);
				continue;
			}

			return -1;
		}

		while ((ret = SQLFetch(hStmt)) != SQL_NO_DATA)
		{
			p = (char *)buf;

			while ((ret = SQLGetData(hStmt, 1, SQL_C_BINARY, BinaryPtr, sizeof(BinaryPtr),
				&LenBin)) != SQL_NO_DATA)
			{
				if (LenBin == SQL_NULL_DATA)
				{
					Clear();
					return 0;
				}

				if (ret == SQL_SUCCESS)
				{
					nGet = LenBin;
				}
				else
				{
					nGet = BLOBBATCH;
				}

				TotalGet += nGet;
				memcpy(p, BinaryPtr, nGet);
				p += nGet;
			}
		}

		Clear();
		return TotalGet;
	}
}

// BLOB data is stored in buf. BLOB data is stored in one of szSQL the Update,
// Insert SQL statements should be.
void CQuery::WriteBlob(LPCTSTR szSQL, void * buf, int size)
{
	SQLINTEGER cbBlob;
	char tmp[BLOBBATCH], *p;
	SQLPOINTER pToken;
	int nPut;
	//LogAdd("%s", szSQL);
	cbBlob = SQL_LEN_DATA_AT_EXEC(size);
	SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY,
		size, 0, (SQLPOINTER)1, 0, &cbBlob);
	SQLExecDirect(hStmt, (SQLCHAR *)szSQL, SQL_NTS);
	ret = SQLParamData(hStmt, &pToken);

	while (ret == SQL_NEED_DATA)
	{
		if (ret == SQL_NEED_DATA)
		{
			if ((int)pToken == 1)
			{
				for(p = (char *)buf; p < (char *)buf + size; p += BLOBBATCH)
				{
					nPut = min(BLOBBATCH, (char *)buf + size - p);
					memcpy(tmp, p, nPut);
					SQLPutData(hStmt, (PTR)tmp, nPut);
				}
			}
		}

		ret = SQLParamData(hStmt, &pToken);
	}

	Clear();
}

int CQuery::BindParameterBinaryOutput(int nCol, BYTE * nValue, int iSize, long * lLength)
{
	int iRet = SQLBindParameter(this->hStmt, nCol, 4, -2, -3, iSize, 0, nValue, iSize, lLength);
	return iRet;
}